/*
(C) 2012, 2016 Percona LLC and/or its affiliates

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
*/

#include "auth_mapping.h"
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include "groups.h"

#include "auth_pam_common.h"

/** Token representation:
    token type, string repr, length of token */
struct token {
  enum class ctype { id, comma, eq, eof } type;
  const char *token;
  size_t token_len;
};

/** Iterator in key-value mapping:
    position and length of key,
    position and length of value,
    current position in string */
struct mapping_iter {
  const char *key;
  size_t key_len;
  const char *value;
  size_t value_len;
  const char *ptr;
};

/** Get next token from buf. Returns new buf position. */
static const char *get_token(struct token *token, const char *buf) {
  const char *ptr = buf;

  while (*ptr && isspace(*ptr)) ++ptr;

  token->token = ptr;
  switch (*ptr) {
    case '\0':
      token->type = token::ctype::eof;
      break;
    case ',':
      token->token_len = 1;
      token->type = token::ctype::comma;
      ++ptr;
      break;
    case '=':
      token->token_len = 1;
      token->type = token::ctype::eq;
      ++ptr;
      break;
    case '"':
      token->token_len = 0;
      ++ptr;
      token->token = ptr;
      while (*ptr && *ptr != '"') {
        ++token->token_len;
        ++ptr;
      }
      token->type = token::ctype::id;
      if (*ptr) ++ptr;
      break;
    default:
      token->token_len = 0;
      while (*ptr && !isspace(*ptr) && *ptr != ',' && *ptr != '=') {
        ++token->token_len;
        ++ptr;
      }
      token->type = token::ctype::id;
  }

  return ptr;
}

/** Create iterator through mapping string.
    Initially iterator set to position before first
    key-value pair. On success non-NULL pointer returned, otherwise NULL */
struct mapping_iter *mapping_iter_new(const char *mapping_string) {
  struct mapping_iter *it = static_cast<mapping_iter *>(
      my_malloc(key_memory_pam_mapping_iter, sizeof(struct mapping_iter), 0));
  if (it != nullptr) {
    it->key = nullptr;
    it->value = nullptr;
    /* eat up service name and move to (, key = value)* part */
    struct token token;
    it->ptr = get_token(&token, mapping_string);
  }
  return it;
}

/** Move iterator to next key-value pair.
    On success pointer to key position in string returned,
    otherwise NULL */
const char *mapping_iter_next(struct mapping_iter *it) {
  struct token token[4] = {{token::ctype::id, 0, 0}};

  /* read next 4 tokens */
  it->ptr = get_token(
      token + 3,
      get_token(token + 2, get_token(token + 1, get_token(token, it->ptr))));

  /* was it ", id = id"? */
  if (!((token[0].type == token::ctype::comma) &&
        (token[1].type == token::ctype::id) &&
        (token[2].type == token::ctype::eq) &&
        (token[3].type == token::ctype::id))) {
    /* we got something inconsistent */
    return nullptr;
  }

  /* set key */
  it->key = token[1].token;
  it->key_len = token[1].token_len;

  /* set value */
  it->value = token[3].token;
  it->value_len = token[3].token_len;

  return it->key;
}

/** Finish iteration and release iterator */
void mapping_iter_free(struct mapping_iter *it) { my_free(it); }

/** Get mapped value for given user name.
    Value is looked up by using all user groups as a key.
    Auth string is iterated only once, while groups are iterated
    for every key-value pair. This is mean than auth string order
    is dominant.

    Example:

    given:
      user "foo" is the member of "wheel", "staff" and "bar".
      auth string is "mysql, root=user1, bar=user2, staff=user3"

    result is "user2".

    On success value_buf returned, otherwise NULL */
char *mapping_lookup_user(const char *user_name, char *value_buf,
                          size_t value_buf_len, const char *mapping_string) {
  /* Iterate through the key-value list stored in auth_string and
  find key (which is interpreted as group name) in the list of groups
  for specified user. If match is found, store appropriate value in
  the authenticated_as field. */
  struct mapping_iter *keyval_it = mapping_iter_new(mapping_string);
  if (keyval_it == nullptr) return nullptr;

  struct groups_iter *group_it = groups_iter_new(user_name);
  if (group_it == nullptr) {
    mapping_iter_free(keyval_it);
    return nullptr;
  }

  const char *key;
  const char *group;
  while ((key = mapping_iter_next(keyval_it)) != nullptr) {
    while ((group = groups_iter_next(group_it)) != nullptr) {
      if (keyval_it->key_len == strlen(group) &&
          strncmp(key, group, keyval_it->key_len) == 0) {
        /* match is found */
        memcpy(value_buf, keyval_it->value,
               std::min(value_buf_len, keyval_it->value_len));
        value_buf[std::min(value_buf_len, keyval_it->value_len)] = '\0';
        groups_iter_free(group_it);
        mapping_iter_free(keyval_it);
        return value_buf;
      }
    }
    groups_iter_reset(group_it);
  }

  groups_iter_free(group_it);
  mapping_iter_free(keyval_it);

  return nullptr;
}

/** Get key in current iterator pos. On success buf returned,
    otherwise NULL */
char *mapping_iter_get_key(struct mapping_iter *it, char *buf, size_t buf_len) {
  if (it->key == nullptr) return nullptr;
  memcpy(buf, it->key, std::min(buf_len, it->key_len));
  buf[std::min(buf_len, it->key_len)] = '\0';
  return buf;
}

/** Get value in current iterator pos. On success buf returned,
    otherwise NULL */
char *mapping_iter_get_value(struct mapping_iter *it, char *buf,
                             size_t buf_len) {
  if (it->value == nullptr) return nullptr;
  memcpy(buf, it->value, std::min(buf_len, it->value_len));
  buf[std::min(buf_len, it->value_len)] = '\0';
  return buf;
}

/** Get value by key. On success pointer to service_name
    returned, otherwise NULL */
char *mapping_get_service_name(char *buf, size_t buf_len,
                               const char *mapping_string) {
  struct token token;

  get_token(&token, mapping_string);
  if (token.type == token::ctype::id) {
    memcpy(buf, token.token, std::min(buf_len, token.token_len));
    buf[std::min(buf_len, token.token_len)] = '\0';
    return buf;
  }

  return nullptr;
}
